/*
 * Copyright (C) 2023 Alexander Novikov
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "serializer.h"
#include <stdlib.h>

int
serializer_init(serializer_t* serializer) {
	if (serializer == NULL) {
		return -1;
	}
	serializer->buffer = malloc(0);
	if (serializer->buffer == NULL) {
		return -1;
	}
	serializer->offset = 0;
	serializer->buffer_size = 0;
	return 0;
}

int
serializer_buffer_resize(serializer_t* serializer, size_t size) {
    if (serializer == NULL) {
        return -1;
    }
    if (serializer->buffer == NULL) {
	return -1;
    }
    serializer->buffer = realloc(serializer->buffer, size);
    if (serializer->buffer == NULL) {
        return -1;
    }
    serializer->buffer_size = size;
    return 0;
}

int
serializer_write_bytes(serializer_t* serializer, uint8_t* data, size_t count) {
    if (serializer == NULL) {
        return -1;
    }
    if ((count + serializer->offset) > serializer->buffer_size && serializer_buffer_resize(serializer, count + serializer->offset) != 0) {
        return -1;
    }
    for (size_t i = 0; i < count; ++i) {
        serializer->buffer[serializer->offset++] = data[i];
    }
    return 0;
}

int
serializer_read_bytes(serializer_t* serializer, uint8_t* data, size_t count) {
    if (serializer == NULL) {
        return -1;
    }
    if ((count + serializer->offset) > serializer->buffer_size) {
        return -1;
    }
    for (size_t i = 0; i < count; ++i) {
        data[i] = serializer->buffer[serializer->offset++];
    }
    return 0;
}

int
serializer_write_byte(serializer_t* serializer, uint8_t data) {
    return serializer_write_bytes(serializer, &data, 1);
}

int
serializer_read_byte(serializer_t* serializer, uint8_t* data) {
    return serializer_read_bytes(serializer, data, 1);
}

int
serializer_write_short(serializer_t* serializer, uint16_t data, int endian) {
    uint8_t temp[2] = { 0 };
    if (endian == Big) {
        temp[0] = (data >> 8) & 0xff;
        temp[1] = data & 0xff;
    } else {
        temp[0] = data & 0xff;
        temp[1] = (data >> 8) & 0xff;
    }
    return serializer_write_bytes(serializer, temp, 2);
}

int
serializer_read_short(serializer_t* serializer, uint16_t *data, int endian) {
    uint8_t temp[2] = { 0 };
    if (serializer_read_bytes(serializer, temp, 2) != 0) {
        return -1;
    }
    if (endian == Big) {
        *data = ((uint16_t)(temp[0] & 0xff) << 8)
            | ((uint16_t)temp[1] & 0xff);
    } else {
        *data = ((uint16_t)temp[0] & 0xff)
            | ((uint16_t)(temp[1] & 0xff) << 8);
    }
    return 0;
}

int
serializer_write_triad(serializer_t* serializer, uint32_t data, int endian) {
    uint8_t temp[3] = {0};
    if (endian == Big) {
        temp[0] = (data >> 16) & 0xff;
        temp[1] = (data >> 8) & 0xff;
        temp[2] = data & 0xff;
    } else {
        temp[0] = data & 0xff;
        temp[1] = (data >> 8) & 0xff;
        temp[2] = (data >> 16) & 0xff;
    }
    return serializer_write_bytes(serializer, temp, 3);
}

int
serializer_read_triad(serializer_t* serializer, uint32_t* data, int endian) {
    uint8_t temp[3] = { 0 };
    if (serializer_read_bytes(serializer, temp, 3) != 0) {
        return -1;
    }
    if (endian == Big) {
        *data = ((uint32_t)(temp[0] & 0xff) << 16)
            | ((uint32_t)(temp[1] & 0xff) << 8)
            | ((uint32_t)temp[2] & 0xff);
    } else {
        *data = ((uint32_t)temp[0] & 0xff)
            | ((uint32_t)(temp[1] & 0xff) << 8)
            | ((uint32_t)(temp[2] & 0xff) << 16);
    }
    return 0;
}

int
serializer_write_int(serializer_t* serializer, uint32_t data, int endian) {
    uint8_t temp[4] = {0};
    if (endian == Big) {
        temp[0] = (data >> 24) & 0xff;
        temp[1] = (data >> 16) & 0xff;
        temp[2] = (data >> 8) & 0xff;
        temp[3] = data & 0xff;
    } else {
        temp[0] = data & 0xff;
        temp[1] = (data >> 8) & 0xff;
        temp[2] = (data >> 16) & 0xff;
        temp[2] = (data >> 24) & 0xff;
    }
    return serializer_write_bytes(serializer, temp, 4);
}

int
serializer_read_int(serializer_t* serializer, uint32_t* data, int endian) {
    uint8_t temp[4] = {0};
    if (serializer_read_bytes(serializer, temp, 4) != 0) {
        return -1;
    }
    if (endian == Big) {
        *data = ((uint32_t)(temp[0] & 0xff) << 24)
            | ((uint32_t)(temp[1] & 0xff) << 16)
            | ((uint32_t)(temp[2] & 0xff) << 8)
            | ((uint32_t)temp[3] & 0xff);
    } else {
        *data = ((uint32_t)temp[0] & 0xff)
            | ((uint32_t)(temp[1] & 0xff) << 8)
            | ((uint32_t)(temp[2] & 0xff) << 16)
            | ((uint32_t)(temp[3] & 0xff) << 24);
    }
    return 0;
}

int
serializer_write_long(serializer_t* serializer, uint64_t data, int endian) {
    uint8_t temp[8] = {0};
    if (endian == Big) {
        temp[0] = (data >> 56) & 0xff;
        temp[1] = (data >> 48) & 0xff;
        temp[2] = (data >> 40) & 0xff;
        temp[3] = (data >> 32) & 0xff;
        temp[4] = (data >> 24) & 0xff;
        temp[5] = (data >> 16) & 0xff;
        temp[6] = (data >> 8) & 0xff;
        temp[7] = data & 0xff;
    } else {
        temp[0] = data & 0xff;
        temp[1] = (data >> 8) & 0xff;
        temp[2] = (data >> 16) & 0xff;
        temp[3] = (data >> 24) & 0xff;
        temp[4] = (data >> 32) & 0xff;
        temp[5] = (data >> 40) & 0xff;
        temp[6] = (data >> 48) & 0xff;
        temp[7] = (data >> 56) & 0xff;
    }
    return serializer_write_bytes(serializer, temp, 8);
}

int
serializer_read_long(serializer_t* serializer, uint64_t* data, int endian) {
    uint8_t temp[8] = {0};
    if (serializer_read_bytes(serializer, temp, 8) != 0) {
        return -1;
    }
    if (endian == Big) {
        *data = ((uint64_t)(temp[0] & 0xff) << 56)
            | ((uint64_t)(temp[1] & 0xff) << 48)
            | ((uint64_t)(temp[2] & 0xff) << 40)
            | ((uint64_t)(temp[3] & 0xff) << 32)
            | ((uint64_t)(temp[4] & 0xff) << 24)
            | ((uint64_t)(temp[5] & 0xff) << 16)
            | ((uint64_t)(temp[6] & 0xff) << 8)
            | ((uint64_t)temp[7] & 0xff);
    } else {
        *data = ((uint64_t)temp[0] & 0xff)
            | ((uint64_t)(temp[1] & 0xff) << 8)
            | ((uint64_t)(temp[2] & 0xff) << 16)
            | ((uint64_t)(temp[3] & 0xff) << 24)
            | ((uint64_t)(temp[4] & 0xff) << 32)
            | ((uint64_t)(temp[5] & 0xff) << 40)
            | ((uint64_t)(temp[6] & 0xff) << 48)
            | ((uint64_t)(temp[7] & 0xff) << 56);
    }
    return 0;
}

int
serializer_write_sockaddr(serializer_t* serializer, struct sockaddr_in* addr) {
        if (addr == NULL) {
		return -1;
	}
	if (addr->sin_family != AF_INET) {
                return -1;
        }
        if (serializer_write_byte(serializer, 4) != 0) {
                return -1;
        }
        if (serializer_write_int(serializer, ~addr->sin_addr.s_addr, 1) != 0) {
                return -1;
        }
        if (serializer_write_short(serializer, htons(addr->sin_port), Big) != 0) {
                return -1;
        }
        return 0;
}

int serializer_read_sockaddr(serializer_t* serializer, struct sockaddr_in* addr) {
        if (addr == NULL) {
                return -1;
        }
        uint32_t temp = 0;
        if (serializer_read_byte(serializer, (uint8_t*)&temp) != 0) {
                return -1;
        }
        if (temp != 4) {
                return -1;
        }
        addr->sin_family = AF_INET;
        if (serializer_read_int(serializer, &temp, 1) != 0) {
                return -1;
        }
        addr->sin_addr.s_addr = ~temp;
        if (serializer_read_short(serializer, (uint16_t*)&temp, Big) != 0) {
                return -1;
        }
        addr->sin_port = htons(temp);
        return 0;
}

int
serializer_deinit(serializer_t* serializer) {
	if (serializer == NULL) {
		return -1;
	}
	if (serializer->buffer == NULL) {
		return -1;
	}
	free(serializer->buffer);
	return 0;
}
